#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "proto.h"

#define IPSTRSIZE 40

int main(int argc,char* argv[])
{
    //取得SOCKET
    //int socket(int domain, int type, int protocol)

    //我要使用AF_INET协议组中的某一个协议来执行SOCK_DGRAM操作
    //如果你不确定你要使用的那个协议的名称，你就写0,
    //写0意味着使用这个协议中对于这个操作支持的默认协议。
    int sd;
    struct sockaddr_in laddr,raddr;
    // laddr -- local address
    // raddr -- remote address
    socklen_t raddr_len;

    char ipstr[IPSTRSIZE];

    struct msg_st rbuf;

    sd = socket(AF_INET,SOCK_DGRAM,0);//这里相当于是用IPv4协议族中的IPPROTO_UDP来实现SOCK_DGRAM的操作
    if(sd < 0)
    {
        perror("socket()");
        exit(1);
    }
    //给SOCKET取得地址
    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(RCVPORT));//我在协议中给的是一个字符串,所以需要转换一下
    //ip地址和网络端口号是要通过网络发送过去的,所以需要考虑字节序的问题,也就是htons

    //inet_pton可以将IPv4 and IPv6 addresse转换成二进制形式
    //因为这个结构体本身就是一个32位的无符号的int类型的整型数,所以可直接写成 laddr.sin_addr,后面的.s_addr可以省略.
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr.s_addr);
    //因为我们现在的ip地址有可能会变化,为了避免ip地址每一次变化,都要进来修改,所以我们给它匹配一个万能地址0.0.0.0
    //我们对"0.0.0.0"的定义是any address.就是说在当前绑定阶段,你自己的ip地址是多少,这四个0就会自动换成你当前的ip地址.

    //这里它要的是struct sockaddr *类型的地址,因此需要转换,
    //但也可以直接转换成(void *)类型.
    //最后一个参数是socklen_t addrlen,指的是你发过来的包含地址的结构体的长度
    if(bind(sd,(struct sockaddr *)&laddr,sizeof(laddr)) < 0)
    {
        perror("bind()");
        exit(1);
    }

    /* 非常重要 */
    raddr_len = sizeof(raddr);

    //收消息
    //我怎么知道,我接没接收到呢,我把它打印到终端上
    //不能转瞬即逝,要把它放到while循环中
    while(1)
    {
        // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
        // 第二个参数表示放哪去,我知道你要给我发结构体来,所以我需要先定义这个结构体类型的变量,然后,将接收到的内容放进去.
        // 第四个参数是我需要记录对端的地址，第五个参数是我需要记录对端地址(所指向的包含地址信息的结构体)的长度。
        // 跟我对话的人的地址格式一定和我是一样的,那它的类型一定也和我一样.
        // 注意第四个参数和第五个参数与bind中的参数要求是有区别的,bind中要填的是包含地址信息的结构体的地址和长度是我自己知道的,
        // 而recvfrom中的参数要求则是与对端通信后,对端回填的,所以是指针的形式.
        recvfrom(sd,&rbuf,sizeof(rbuf),0,(struct sockaddr *)&raddr,&raddr_len);
        // 在man手册中有提到：
        // 需要特别注意的是,这里的raddr_len要给它初始化,否则是会出错的,但出错只有第一次出错,
        // 因为第一次填完地址之后,发现地址长度不对, 该值就会被更新到正确的值.

        // 转换IPv4和IPv6地址从二进制到文本形式
        inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
        // 将整数转换为点分十进制
        printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));//我的消息是从哪个地址哪个端口来的
        printf("NAME = %s.\n",rbuf.name);//单字节传输不涉及到大端小端的存储情况
        printf("MATH = %d.\n",ntohl(rbuf.math));
        printf("CHINESE = %d.\n",ntohl(rbuf.chinese));
    }
    //关闭socket
    close(sd);

    return 0;
}

